#ifndef DiffAlgorithmBSDiff_HPP
#define DiffAlgorithmBSDiff_HPP

#include "DiffAlgorithm.hpp"

#include <sys/types.h>

extern"C"{
#include <bzlib.h>
#include <bsdiff.h>
}

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>

#ifndef _WIN32
#include "err.h"
#define O_BINARY 0x8000
#else
    static void err(int i, ...)
    {
        cout << "dfghgh" << endl;
        exit(i);
    }
    static void errx(int i, ...)
    {
        cout << "xxxdfghgh" << endl;
        exit(i);
    }
#endif // _WIN32

class DiffAlgorithmBSDiff : public DiffAlgorithm
{
public:

    /*
    https://github.com/amireh/Karazeh/blob/master/src/bsdiff.cpp
    or take a look at
    https://github.com/HoverRace/HoverRace/blob/master/updater/bsdiff/bsdiff.cpp
    or
    https://chromium.googlesource.com/chromium/src/courgette/+/master/third_party/bsdiff_create.cc
    I use MinGW GCC on Windows, so...
    */

    static int bz2_write(struct bsdiff_stream* stream, const void* buffer, int size)
    {
        int bz2err;
        BZFILE* bz2;

        bz2 = (BZFILE*)stream->opaque;
        BZ2_bzWrite(&bz2err, bz2, (void*)buffer, size);
        if (bz2err != BZ_STREAM_END && bz2err != BZ_OK)
            return -1;

        return 0;
    }

    static void offtout(int64_t x, uint8_t *buf)
    {
        int64_t y;

        if(x < 0) y = -x;
        else y = x;

        buf[0] = y % 256;
        y -= buf[0];
        y = y / 256;
        buf[1] = y % 256;
        y -= buf[1];
        y = y / 256;
        buf[2] = y % 256;
        y -= buf[2];
        y = y / 256;
        buf[3] = y % 256;
        y -= buf[3];
        y = y / 256;
        buf[4] = y % 256;
        y -= buf[4];
        y = y / 256;
        buf[5] = y % 256;
        y -= buf[5];
        y = y / 256;
        buf[6] = y % 256;
        y -= buf[6];
        y = y / 256;
        buf[7] = y % 256;

        if(x < 0) buf[7] |= 0x80;
    }

    int Do(const string &old_file, const string &new_file, const string &patch_file)
    {
        cout << "BSDiff!" << endl;

        int fd;
        int bz2err;
        uint8_t *old_buf, *new_buf;
        off_t oldsize, newsize;
        uint8_t buf[8];
        FILE * pf;
        struct bsdiff_stream stream;
        BZFILE* bz2;
        cout << "BSDiff! 1" << endl;
        memset(&bz2, 0, sizeof(bz2));
        stream.malloc = malloc;
        stream.free = free;
        stream.write = bz2_write;
        cout << "BSDiff! 2" << endl;
        //if(argc != 4) errx(1, "usage: %s oldfile newfile patchfile\n", argv[0]);

        /* Allocate oldsize+1 bytes instead of oldsize bytes to ensure
        	that we never try to malloc(0) and get a NULL pointer */
        if(((fd = open(old_file.c_str(), O_RDONLY | O_BINARY, 0)) < 0) ||
                ((oldsize = lseek(fd, 0, SEEK_END)) == -1) ||
                ((old_buf = (uint8_t*)malloc(oldsize + 1)) == NULL) ||
                (lseek(fd, 0, SEEK_SET) != 0) ||
                (read(fd, old_buf, oldsize) != oldsize) ||
                (close(fd) == -1)) err(1, "%s", old_file.c_str());
        cout << "after oldfile!" << endl;
        /* Allocate newsize+1 bytes instead of newsize bytes to ensure
        	that we never try to malloc(0) and get a NULL pointer */
        if(((fd = open(new_file.c_str(), O_RDONLY | O_BINARY, 0)) < 0) ||
                ((newsize = lseek(fd, 0, SEEK_END)) == -1) ||
                ((new_buf = (uint8_t*)malloc(newsize + 1)) == NULL) ||
                (lseek(fd, 0, SEEK_SET) != 0) ||
                (read(fd, new_buf, newsize) != newsize) ||
                (close(fd) == -1)) err(1, "%s", new_file.c_str());
        cout << "after newfile!" << endl;
        /* Create the patch file
        Notice that on Windows you should specify "wb", for binary mode */
        if ((pf = fopen(patch_file.c_str(), "wb")) == NULL)
            err(1, "%s", patch_file.c_str());
        cout << "after patch!" << endl;
        /* Write header (signature+newsize)*/
        offtout(newsize, buf);
        if (fwrite("PEAR/BSDIFF43", 13, 1, pf) != 1 ||
                fwrite(buf, sizeof(buf), 1, pf) != 1)
            err(1, "Failed to write header");


        if (NULL == (bz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)))
            errx(1, "BZ2_bzWriteOpen, bz2err=%d", bz2err);

        stream.opaque = bz2;
        if (bsdiff(old_buf, oldsize, new_buf, newsize, &stream))
            err(1, "bsdiff");

        BZ2_bzWriteClose(&bz2err, bz2, 0, NULL, NULL);
        if (bz2err != BZ_OK)
            err(1, "BZ2_bzWriteClose, bz2err=%d", bz2err);

        if (fclose(pf))
            err(1, "fclose");

        /* Free the memory we used */
        free(old_buf);
        free(new_buf);

        return 0;
    }
};

#endif // DiffAlgorithmBSDiff_HPP
